<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>PDBe Molstar</title>

    <!-- Molstar CSS & JS -->
    <link rel="stylesheet" type="text/css" href="./build/pdbe-molstar-light.css">
    <script type="text/javascript" src="./build/pdbe-molstar-plugin.js"></script>

    <style>
        body {
            padding: 30px;
        }

        #myViewer {
            width: 600px;
            height: 600px;
            position: relative;
            margin-top: 100px;
            margin-right: 20px;
        }

        .item {
            margin-block: 8px;
            padding-block: 2px;
            padding-inline: 8px;
            background-color: gainsboro;
            cursor: pointer;
        }

        .spinner_aj0A {
            transform-origin: center;
            animation: spinner_KYSC .75s infinite linear
        }

        @keyframes spinner_KYSC {
            100% {
                transform: rotate(360deg)
            }
        }
    </style>
</head>

<body>
    <h3>PDBe Mol* JS Plugin Demo</h3>
    <div class="viewerSection" style="display: flex; flex-direction: row;">
        <!-- Molstar container -->
        <div id="myViewer"></div>
        <div style="display: block;">
            <div style="height: 24px; margin-bottom: 24px;">
                <svg id="loading-spinner" style="display: none;" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                    <path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_aj0A" />
                </svg>
            </div>
            <h4>Subcomplexes:</h4>
            <div id="subcomplex-list"></div>
            <h4>Supercomplexes:</h4>
            <div id="supercomplex-list"></div>
        </div>
    </div>

    <script>
        function getJsonParam(paramName) {
            const paramString = new URL(window.location).searchParams.get(paramName);
            const param = JSON.parse(paramString);
            return param ?? undefined;
        }

        async function getComplexApiData(complexId) {
            const apiUrl = `https://www.ebi.ac.uk/pdbe/aggregated-api/complex/details/${complexId}?id_type=pdb_complex_id`;
            const response = await fetch(apiUrl);
            if (!response.ok) throw new Error(`Fetching API data failed with status code ${response.status}: ${apiUrl}`);
            const data = await response.json();
            if (data[complexId]?.length !== 1) throw new Error('API data must contain exactly one result.');
            return data[complexId][0];
        }

        async function initComplexView(complexId) {
            const data = await getComplexApiData(complexId);
            console.log('Complex ID:', complexId);
            const { pdb_id, assembly_id } = data.representative_structure;
            console.log(`Structure: ${pdb_id}, assembly ${assembly_id}`);
            baseComponents = data.participants.map(p => p.accession);
            baseRfamMappings = await getRfamMappings(pdb_id);
            console.log(`Rfam mappings for ${pdb_id}:`, baseRfamMappings);

            // Create plugin instance
            viewerInstance = new PDBeMolstarPlugin();
            const viewerContainer = document.getElementById('myViewer');

            const defaultOptions = {
                bgColor: 'white',
                sequencePanel: true,
                hideStructure: ['water'],
            };
            const options = {
                ...defaultOptions,
                ...getJsonParam('options'),
                moleculeId: pdb_id,
                assemblyId: assembly_id,
                customData: undefined,
            };
            await viewerInstance.render(viewerContainer, options);
            const sub = viewerInstance.events.loadComplete.subscribe(() => {
                sub.unsubscribe();
                resetColoring();
            });

            const supercomplexesDiv = document.getElementById('supercomplex-list');
            for (const supercomplex of data.supercomplexes) {
                addItem(supercomplexesDiv, supercomplex, 'supercomplex');
            }
            const subcomplexesDiv = document.getElementById('subcomplex-list');
            for (const subcomplex of data.subcomplexes) {
                addItem(subcomplexesDiv, subcomplex, 'subcomplex');
            }
        }

        function addItem(listDiv, id, kind) {
            const item = document.createElement('div');
            item.innerHTML = `<input type="checkbox" id="${id}" onclick="toggleComplex(id, this.checked, '${kind}')" /> <label for="${id}">${id}</label> <a href="${urlForComplex(id)}" style="text-decoration: none;">&#10138;</a>`;
            listDiv.append(item);
        }

        async function toggleComplex(id, on, kind) {
            const checkbox = document.getElementById(id);
            checkbox.setAttribute('disabled', '');
            try {
                if (on) {
                    await loadComplex(id, kind);
                } else {
                    await deleteComplex(id);
                }
            } finally {
                checkbox.removeAttribute('disabled');
            }
        }

        async function loadComplex(id, kind) {
            const data = await getComplexApiData(id);
            const { pdb_id, assembly_id } = data.representative_structure;
            const otherComponents = data.participants.map(p => p.accession);
            console.log(`Superposing ${id} (${pdb_id}, assembly ${assembly_id}) containing ${otherComponents}, method: ${superpositionMethod}`);
            rfamMappings[pdb_id] ??= await getRfamMappings(pdb_id);
            console.log(`Rfam mappings for ${pdb_id}:`, rfamMappings[pdb_id]);
            const result = await PDBeMolstarPlugin.extensions.Complexes.loadComplexSuperposition(viewerInstance, {
                id,
                pdbId: pdb_id, assemblyId: assembly_id,
                baseComponents, otherComponents,
                coloring: kind, animationDuration: 250,
                coreColor, unmappedColor,
                baseMappings: baseRfamMappings,
                otherMappings: rfamMappings[pdb_id],
                method: superpositionMethod,
            });
            if (result.status === 'success') console.log(`Superposed complexes, RMSD ${result.superposition.rmsd}`, result.superposition);
            if (result.status === 'zero-overlap') console.warn(`Failed to superpose complexes due to insufficient Uniprot overlap`);
            if (result.status === 'failed') console.warn(`Failed to superpose complexed`);
        }

        async function deleteComplex(id) {
            await viewerInstance.deleteStructure(id);
            await viewerInstance.visual.reset({ camera: true });
            await new Promise(resolve => setTimeout(resolve, 300)); // 300ms = 50ms after camera reset completed
            await resetColoring();
        }

        async function resetColoring() {
            await PDBeMolstarPlugin.extensions.Complexes.Coloring.colorComponents(viewerInstance, { structId: 'main', components: baseComponents, mappings: baseRfamMappings, coreColor, unmappedColor });
        }

        async function getRfamMappings(pdbId) {
            const url = `https://www.ebi.ac.uk/pdbe/api/nucleic_mappings/${pdbId}`;
            const response = await fetch(url);
            if (!response.ok && response.status !== 404) throw new Error(`API call failed with code ${response.status} (${url})`);
            const js = response.status === 404 ? {} : await response.json();
            const rfamData = js[pdbId]?.Rfam ?? {};

            const result = {};
            for (const family of Object.keys(rfamData).sort()) {
                result[family] = rfamData[family].mappings.map(chunk => ({
                    entity_id: String(chunk.entity_id),
                    struct_asym_id: chunk.struct_asym_id,
                    start_residue_number: chunk.start.residue_number,
                    end_residue_number: chunk.end.residue_number,
                }));
            }
            return result;
        }

        function urlForComplex(complexId) {
            const url = new URL(window.location);
            url.searchParams.set('complexId', complexId);
            return url.toString();
        }

        const DEFAULT_COMPLEX_ID = 'PDB-CPX-159519';
        // example of ugly superposition: PDB-CPX-159519 with supercomplex PDB-CPX-283620

        const complexId = new URL(window.location).searchParams.get('complexId');
        if (!complexId) {
            window.location = urlForComplex(DEFAULT_COMPLEX_ID);
        }
        const coreColor = new URL(window.location).searchParams.get('coreColor') ?? undefined;
        const unmappedColor = new URL(window.location).searchParams.get('unmappedColor') ?? undefined;
        const superpositionMethod = new URL(window.location).searchParams.get('method') ?? 'biggest-matched-chain';


        let viewerInstance; // PDBeMolstarPlugin
        let baseComponents; // string[]
        let baseRfamMappings = {}; // { [accession: string]: QueryParam[] }
        const rfamMappings = {}; // { [pdbId: string]: { [accession: string]: QueryParam[] } }

        initComplexView(complexId);

    </script>

</body>

</html>